package org.gwt.grin.client;

import org.gwt.grin.client.commands.ActivateSegmentCommand;
import org.gwt.grin.client.commands.Command;
import org.gwt.grin.client.input.RCHandler;
import org.gwt.grin.client.input.RCKeyEvent;
import org.gwt.grin.client.util.Graphics;

import com.google.gwt.core.client.GWT;

/**
 * A segment within a show. A show is composed of segments, and at all times
 * exactly one segment is active. When a segment is active, its features are
 * showing, TODO...
 * <p>
 * When a new feature is activated, any features that are active in both
 * segments are not re-initialized, so that e.g. animations will just continue.
 * 
 * @author Bill Foote (http://jovial.com)
 * @author ggeorg
 */
public class Segment implements Node {

	protected Show show;

	protected final String name;
	protected final Feature[] activeFeatures;
	private boolean[] featureWasActivated;
	protected final Feature[] settingUpFeatures;
	protected final Command[] onEntryCommands;
	protected final boolean nextOnSetupDone;
	protected final Command[] nextCommands;
	protected final RCHandler[] rcHandlers;

	/**
	 * The bit mask is formed by or-ing the set of remote control key presses
	 * this segment has handlers for.
	 */
	protected int rcPressedInterest;

	/**
	 * The bit mask is formed by or-ing the set of remote control key releases
	 * this segment has handlers for.
	 */
	protected int rcReleasedInterest;

	private boolean active = false;
	private boolean segmentSetupComplete;

	private ActivateSegmentCommand cmdToActivate;
	private ActivateSegmentCommand cmdToActivatePush;

	private int outstandingSetups;

	// # of features in setup clause and activate clause that have
	// been checked so far for setup
	private int setupCheckedInSetup;
	private int setupCheckedInActive;

	public Segment(String name, Feature[] activeFeatures,
			Feature[] settingUpFeatures, RCHandler[] rcHandlers,
			int rcPressedInterest, int rcReleasedInterest,
			Command[] onEntryCommands, boolean nextOnSetupDone,
			Command[] nextCommands) {
		this.name = name;
		this.activeFeatures = activeFeatures;
		this.settingUpFeatures = settingUpFeatures;
		this.rcHandlers = rcHandlers;
		this.rcPressedInterest = rcPressedInterest;
		this.rcReleasedInterest = rcReleasedInterest;
		this.onEntryCommands = onEntryCommands;
		this.nextOnSetupDone = nextOnSetupDone;
		this.nextCommands = nextCommands;
	}
	
	@Override
	public String toString() {
		if (name == null) {
			return "segment @" + Integer.toHexString(hashCode());
		} else {
			return "segment " + name;
		}
	}

	public Show getShow() {
		return show;
	}

	// package-private
	void setShow(Show show) {
		this.show = show;
	}

	/**
	 * Get the name of this segment. Public segments all have names. Private
	 * segments may have names.
	 * 
	 * @return the segment's name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Initialize up this segment. This is called on show initialization. A show
	 * will initialize all of its segments before it initializes the features.
	 */
	void initialize() {
		featureWasActivated = new boolean[activeFeatures.length];
		for (int i = 0; i < featureWasActivated.length; i++) {
			featureWasActivated[i] = false;
		}
	}

	/**
	 * For use with {@link Show#activateSegment(Segment, boolean)}. We create it
	 * lazily, but if we ever create a command we keep it, to avoid creating
	 * garbage. The same command is pretty likely to be used multiple times.
	 * 
	 * @param push
	 * @return
	 */
	Command getCommandToActivate(boolean push) {
		if (push) {
			if (cmdToActivatePush == null) {
				cmdToActivatePush = new ActivateSegmentCommand(show, true,
						false);
				cmdToActivatePush.setup(this);
			}
			return cmdToActivatePush;
		} else {
			if (cmdToActivate == null) {
				cmdToActivate = new ActivateSegmentCommand(show);
				cmdToActivate.setup(this);
			}
			return cmdToActivate;
		}
	}

	/**
	 * Activate this segment, that is, cause it to start presenting. This will
	 * not take long; all real work is deferred to worker threads.
	 * 
	 * @param lastSegment
	 *            the last segment we're coming from
	 */
	void activate(Segment lastSegment) {
		if (GWT.isClient()) {
			GWT.log("Going from segment " + lastSegment + " to " + this);
		}
		if (lastSegment == this) {
			return;
		}
		active = true;
		segmentSetupComplete = false;
		outstandingSetups = 0;
		setupCheckedInSetup = 0;
		setupCheckedInActive = 0;
		/*
		 * TODO When all of the Segment's active features finish their set up in
		 * runFeatureSetup(), showTopGroup's parts are updated to reflect this
		 * Segment's active feature array.
		 * 
		 * show.showTopGroup.resetVisiblePartsNoAssert(null);
		 */
		for (int i = 0; i < activeFeatures.length; i++) {
			int needed = activeFeatures[i].setup();
			outstandingSetups += needed;

			if (needed > 0 || activeFeatures[i].needsMoreSetup()) {
				GWT.log("WARNING: Feature " + activeFeatures[i]
						+ " in segment " + name + " wasn't set up on time.");
			}

			if (!activeFeatures[i].needsMoreSetup()) {
				activeFeatures[i].activate();
				featureWasActivated[i] = true;
			}
		}
		for (int i = 0; i < settingUpFeatures.length; i++) {
			outstandingSetups += settingUpFeatures[i].setup();
			// Our count of outstanding setups might be low, if some features
			// had already started setting up in a previous segment, but it will
			// never be high. If it's low, the result will be some wasted CPU
			// time, but correct behavior.
		}
		if (lastSegment != null) {
			lastSegment.deactivate();
		}
		if (rcHandlers != null) {
			for (int i = 0; i < rcHandlers.length; i++) {
				rcHandlers[i].activate(this);
			}
		}
		if (onEntryCommands != null) {
			show.runCommands(onEntryCommands);
		}
		outstandingSetups++; // The one we set up on the next line...
		runFeatureSetup();
	}

	/**
	 * Called when another segment is activated, and called on the active
	 * segment when the show is destroyed.
	 */
	void deactivate() {
		active = false;
		for (int i = 0; i < activeFeatures.length; i++) {
			if (featureWasActivated[i]) {
				activeFeatures[i].deactivate();
				featureWasActivated[i] = false;
			}
			activeFeatures[i].unsetup();
		}
		for (int i = 0; i < settingUpFeatures.length; i++) {
			settingUpFeatures[i].unsetup();
		}
		// TODO show.showTopGroup.resetVisiblePartsNoAssert(null);
	}

	/**
	 * When a feature is setup, we get this call. We have to be a little
	 * conservative; it's possible that a feature from a previous, stale segment
	 * could finish its setup after we become the current segment, so this call
	 * really means "one of our features probably finished setup, but we'd
	 * better check to be sure".
	 */
	void runFeatureSetup() {
		// Check to see if all features in active clause are set up
		if (setupCheckedInActive < activeFeatures.length) {
			while (setupCheckedInActive < activeFeatures.length) {
				if (!featureWasActivated[setupCheckedInActive]
						&& activeFeatures[setupCheckedInActive]
								.needsMoreSetup()) {
					return;
				}
				setupCheckedInActive++;
			}
			for (int i = 0; i < activeFeatures.length; i++) {
				if (!featureWasActivated[i]) {
					activeFeatures[i].activate();
					featureWasActivated[i] = true;
				}
			}

			// TODO show top group
		}

		outstandingSetups--;
		// This can actually go negative -- see the comment where
		// it's incremented to see why.
		if (!active || outstandingSetups > 0 || segmentSetupComplete) {
			return;
		}

		// Check if the setup clause is really finished.
		while (setupCheckedInSetup < settingUpFeatures.length) {
			if (settingUpFeatures[setupCheckedInSetup].needsMoreSetup()) {
				return;
			}
			setupCheckedInSetup++;
		}

		segmentSetupComplete = true;

		// Now check to see if we should send the next command(s).
		// At this point in the code, all of our features are set up,
		// so we send the next command if we have no active features,
		// or if nextOnSetupDone is true. nextOnSetupDone will be true
		// if our "next" clause was called "setup_done".
		//
		// A "next" clause gets the "setup_done" behavior because originally,
		// there were only "next" clauses, the idea being that a segment
		// with no active features could only reasonably be used for
		// setup. Later, "setup_done" was added, e.g. to allow a "loading"
		// animation, but the old behavior was kept for backwards
		// compatibility.

		if (nextOnSetupDone || activeFeatures.length == 0) {
			doSegmentDone();
		}
	}

	void doSegmentDone() {
		/*
		 * The "segment done" command is sent from a feature within the model
		 * update loop; if the next command moves us to a new segment, that will
		 * prevent us from getting a second one due to finishing setup. If it
		 * *doesn't* move us to a new segment, then maybe the show author means
		 * to send the next command more than once.
		 */
		if (nextCommands != null) {
			show.runCommands(nextCommands);
		}
	}

	//
	// Called from Show
	//
	void paintFrame(Graphics gc) {
		for (int i = 0; i < activeFeatures.length; i++) {
			activeFeatures[i].paintFrame(gc);
		}
	}

	//
	// Called from Show
	//
	void nextFrameForActiveFeatures() {
		for (int i = 0; i < activeFeatures.length; i++) {
			activeFeatures[i].nextFrame();
		}
	}

	//
	// Called from Show
	//
	void nextFrameForRCHandlers() {
		if (rcHandlers != null) {
			for (int i = 0; i < rcHandlers.length; i++) {
				rcHandlers[i].nextFrame();
			}
		}
	}

	//
	// Called from Show
	//
	boolean handleKeyPressed(RCKeyEvent re, Show caller) {
		if (rcHandlers == null) {
			return false;
		}
		for (RCHandler h : rcHandlers) {
			if (h.handleKeyPressed(re, caller)) {
				return true;
			}
		}
		return false;
	}

	//
	// Called from Show
	//
	boolean handleKeyReleased(RCKeyEvent re, Show caller) {
		if (rcHandlers == null) {
			return false;
		}
		for (RCHandler h : rcHandlers) {
			if (h.handleKeyReleased(re, caller)) {
				return true;
			}
		}
		return false;
	}

	//
	// Called from show
	//
	boolean handleMouse(int x, int y, boolean activate) {
		if (rcHandlers == null) {
			return false;
		}
		boolean handled = false;
		for (RCHandler h : rcHandlers) {
			if (h.handleMouse(x, y, activate)) {
				handled = true;
			}
		}
		return handled;
	}

	/**
	 * Get the list of active features of this segment. Normally client code
	 * shouldn't call this, but it is needed for building debugging tools.
	 * 
	 * @return a list of active features of this segment
	 */
	public Feature[] getActiveFeatures() {
		return activeFeatures;
	}

	/**
	 * Get the list of features in the setup clause of this segment. Normally
	 * client code shouldn't call this, but it is needed for building debugging
	 * tools.
	 * 
	 * @return a list of features in the setup clause
	 */
	public Feature[] getSettupFeatures() {
		return settingUpFeatures;
	}

	/**
	 * Get the list of commands that are called when this segment is activated.
	 * Normally client code shouldn't call this, but it is needed for building
	 * debugging tools.
	 * 
	 * @return a list of commands that are called when this segment is activated
	 */
	public Command[] getOnEntryCommands() {
		return onEntryCommands;
	}

	/**
	 * Do we trigger the commands in our next clause when all of the features in
	 * out setup clause have finished loading?
	 * 
	 * @return the answer to that question
	 */
	public boolean isNextOnSetupDone() {
		return nextOnSetupDone;
	}

	/**
	 * Give the commands in our next clause. This can be triggered by setup
	 * being done, or by a segment_done command. Normally client code shouldn't
	 * call this, but it is needed for building debugging tools.
	 * 
	 * @see #isNextOnSetupDone()
	 * @see Show#segmentDone()
	 * @return the commands in next clause
	 */
	public Command[] getNextCommands() {
		return nextCommands;
	}

	/**
	 * Give the set of remote control handlers for this segment. Normally client
	 * code shouldn't call this, but it is needed for building debugging tools,
	 * like grinview.
	 **/
	public RCHandler[] getRCHandlers() {
		return rcHandlers;
	}

	/**
	 * TODO javadoc
	 */
	public void paintDone() {
		for (int i = 0; i < activeFeatures.length; i++) {
			activeFeatures[i].paintDone();
		}
	}

}
